/*
 * Copyright (C) 2010 Leandro Aparecido <lehphyro@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.code.mjl;

import java.util.concurrent.*;

import com.google.code.mjl.factory.*;
import com.google.code.mjl.formatter.*;
import com.google.code.mjl.formatter.impl.*;
import com.google.code.mjl.impl.*;

public class LogManager {

	public static final String FACTORY_CLASS_NAME = "mjl.log_adapter.factory";
	public static final String FORMATTER_FACTORY_CLASS_NAME = "mjl.formatter.factory";
	public static final String ASYNC_MESSAGE_QUEUE_SIZE = "mjl.async.queue_size";
	public static final String PREPEND_MESSAGE_CODES = "mjl.message_code.prepend";
	public static final String MESSAGE_CODE_SIZE = "mjl.message_code.size";

	private static boolean disableShutdownHook;
	private static LogAdapterFactory logAdapterFactory;
	private static MessageFormatterFactory messageFormatterFactory;
	private static boolean prependMessageCodes;
	private static int messageCodeSize;

	private static int asyncQueueSize;
	private static ExecutorService asyncExecutor;
	private static boolean usedAsync;

	private static class AsyncLoggerHolder {
		static final AsyncLogger logger = new AsyncLogger(asyncExecutor, asyncQueueSize);
		static {
			usedAsync = true;
		}
	}
	private static AsyncLogger asyncLogger() { return AsyncLoggerHolder.logger; }

	static {
		logAdapterFactory = init(FACTORY_CLASS_NAME,
							 "com.google.code.mjl.factory.Log4jLogAdapterFactory",
							 "Cannot initialize internal logger factory:",
							 new StdoutLogAdapterFactory());
		messageFormatterFactory = init(FORMATTER_FACTORY_CLASS_NAME,
									   "com.google.code.mjl.formatter.impl.DefaultMessageFormatterFactory",
									   "Cannot initialize message formatter factory:",
									   new DefaultMessageFormatterFactory());
		asyncQueueSize = (int)init(ASYNC_MESSAGE_QUEUE_SIZE, 100000, "Cannot initialize async message queue size:");
		prependMessageCodes = init(PREPEND_MESSAGE_CODES, false);
		messageCodeSize = (int)init(MESSAGE_CODE_SIZE, 5, "Could not parse message code size:");

		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				if (!disableShutdownHook) {
					shutdown();
				}
			}
		});
	}

	public static void enableExplicitShutdown() {
		disableShutdownHook = true;
	}
	
	public static void enableMessageCodes() {
		prependMessageCodes = true;
	}

	public static LogAdapter createLogAdapter(String logName) {
		return logAdapterFactory.create(logName);
	}

	public static void log(LogEvent logEvent, boolean async) {
		if (async) {
			logAsync(logEvent);
		} else if (logEvent.logger().isEnabled(logEvent.level())) {
			logEvent.logger().log(logEvent);
		}
	}

	public static void logAsync(LogEvent logEvent) {
		if (logEvent.logger().isEnabled(logEvent.level())) {
			asyncLogger().add(logEvent);
		}
	}

	/**
	 * Logging during execution of this method is not guaranteed to take effect.
	 */
	public static void shutdown() {
		if (usedAsync) {
			asyncLogger().shutdown();
		}
		logAdapterFactory.shutdown();
	}

	@SuppressWarnings("unchecked")
	private static <T> T init(String propertyName, String defaultPropertyValue, String errorMessage, T defaultValue) {
		try {
			String value = System.getProperty(propertyName, defaultPropertyValue);
			Class<?> clazz = Class.forName(value);
			return (T)clazz.newInstance();
		} catch (Throwable t) {
			System.err.println(errorMessage);
			t.printStackTrace();
			return defaultValue;
		}
	}

	private static long init(String propertyName, long defaultValue, String errorMessage) {
		try {
			String value = System.getProperty(propertyName);
			if (value == null) {
				return defaultValue;
			}
			return Long.parseLong(value);
		} catch (NumberFormatException e) {
			System.err.println(errorMessage);
			e.printStackTrace();
			return defaultValue;
		}
	}

	private static boolean init(String propertyName, boolean defaultValue) {
		String value = System.getProperty(propertyName);
		if (value == null) {
			return defaultValue;
		}
		return Boolean.parseBoolean(value);
	}

// Getters/Setters

	public static LogAdapterFactory getLogAdapterFactory() {
		return logAdapterFactory;
	}

	public static void setLogAdapterFactory(LogAdapterFactory logAdapterFactory) {
		LogManager.logAdapterFactory = logAdapterFactory;
	}

	public static MessageFormatterFactory getMessageFormatterFactory() {
		return messageFormatterFactory;
	}

	public static void setMessageFormatterFactory(MessageFormatterFactory messageFormatterFactory) {
		LogManager.messageFormatterFactory = messageFormatterFactory;
	}

	public static Executor getAsyncExecutor() {
		return asyncExecutor;
	}

	public static void setAsyncExecutor(ExecutorService asyncExecutor) {
		LogManager.asyncExecutor = asyncExecutor;
	}
	
	public static boolean isPrependMessageCodes() {
		return prependMessageCodes;
	}
	
	public static int getMessageCodeSize() {
		return messageCodeSize;
	}
	
	public static void setMessageCodeSize(int messageCodeSize) {
		LogManager.messageCodeSize = messageCodeSize;
	}
}
